#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "defs.h"
#include "externs.h"
#include "protos.h"

int  mopt;
int  in_macro;
int  expand_macro;
char marg[8][10][80];
int  midx;
int  mcounter, mcntmax;
int  mcntstack[8];
struct t_line  *mstack[8];
struct t_macro *macro_tbl[256];
struct t_line* mlptr = NULL;
struct t_macro *mptr = NULL;

/* .macro pseudo */

void
do_macro(int *ip)
{
    if (pass == LAST_PASS)
        println();
    else {
        /* error checking */
        if (expand_macro) {
            error("Can not nest macro definitions!");
            return;
        }
        if (lablptr == NULL) {
            /* skip spaces */
            while (isspace(prlnbuf[*ip]))
                (*ip)++;

            /* search a label after the .macro */
            if (colsym(ip) == 0) {
                error("No name for this macro!");
                return;
            }

            /* put the macro name in the symbol table */
            if ((lablptr = stlook(1)) == NULL)
                return;
        }
        if (lablptr->refcnt) {
            switch (lablptr->type) {
            case MACRO:
                fatal_error("Macro already defined!");
                return;

            case FUNC:
                fatal_error("Symbol already used by a function!");
                return;

            default:
                fatal_error("Symbol already used by a label!");
                return;
            }
        }
        if (!check_eol(ip))
            return;

        /* install this new macro in the hash table */
        if (!macro_install())
            return;
    } 
    in_macro = 1;
}

/* .endm pseudo */

void
do_endm(int *ip)
{
    error("Unexpected ENDM!");
    return;
}

/* search a macro in the hash table */

struct t_macro *macro_look(int *ip)
{
    struct t_macro *ptr;
    char name[32];
    unsigned char c;
    int  hash;
    int  l;

    /* calculate the symbol hash value and check syntax */
    l = 0;
    hash = 0;
    for (;;) {
        c = prlnbuf[*ip];
        if (c == '\0' || c == ' ' || c == '\t' || c == ';')
            break;
        if (!isalnum(c) && c != '_')
            return (NULL);
        if (l == 0) {
            if (isdigit(c))
                return (NULL);
        }
        if (l == 31)
            return (NULL);
        name[l++] = c;
        hash += c;
        hash  = (hash << 3) + (hash >> 5) + c;
        (*ip)++;
    }
    name[l] = '\0';
    hash &= 0xFF;

    /* browse the hash table */
    ptr = macro_tbl[hash];
    while (ptr) {
        if (!strcmp(name, ptr->name))
            break;            
        ptr = ptr->next;
    }

    /* return result */
    return (ptr);
}

/* extract macro arguments */

int
macro_getargs(int ip)
{
    char *ptr;
    char  c, t;
    int   i, j, f, arg;
    int   level;

    /* can not nest too much macros */
    if (midx == 7) {
        error("Too many nested macro calls!");
        return (0);
    }

    /* initialize args */
    mcntstack[midx] = mcounter;
    mstack[midx++] = mlptr;
    ptr = marg[midx][0];
    arg = 0;

    for (i = 0; i < 9; i++)
        marg[midx][i][0] = '\0';

    /* extract args */
    for (;;) {
        /* skip spaces */
        while (isspace(prlnbuf[ip]))
            ip++;

        c = prlnbuf[ip++];
        switch (c) {
        /* no arg */
        case ',':
            arg++;
            ptr = marg[midx][arg];
            if (arg == 9) {
                error("Too many arguments for a macro!");
                return (0);
            }
            break;

        /* string */            
        case '{':
            c = '}';
        case '\"':
            i = 0;
            if (c == '\"')
                ptr[i++] = c;
            for (;;) {
                t = prlnbuf[ip++];
                if (t == '\0') {
                    error("Unterminated string!");
                    return (0);
                }
                if (i == 80) {
                    error("String too long, max. 80 characters!");
                    return (0);
                }
                if (t == c)
                    break;
                ptr[i++] = t;
            }
            if (c == '\"')
                ptr[i++] = t;

            /* skip spaces */
            while (isspace(prlnbuf[ip]))
                ip++;

            /* check end of arg */
            switch (prlnbuf[ip]) {
            case '\0':
            case ',':
            case ';':
                break;

            default:
                error("Syntax error!");
                return (0);
            }

            /* end arg string */
            ptr[i] = '\0';
            break;

        /* end of line */    
        case ';':
        case '\0':
            return (1);

        /* continuation char */
        case '\\':
            /* skip spaces */
            i = ip;
            while (isspace(prlnbuf[i]))
                i++;

            /* check */
            if (prlnbuf[i] == ';' || prlnbuf[i] == '\0') {
                /* output line */
                if (pass == LAST_PASS) {
                    println();
                    clearln();
                }

                /* read a new line */
                if (readline() == -1)
                    return (0);
                
                /* rewind line pointer and continue */
                ip = SFIELD;
                break;
            }

        /* other */
        default:
            i = 0;
            j = 0;
            f = 0;
            level = 0;
            while (c) {
                if (c == ',') {
                    if (level == 0)
                        break;
                }
                else if ((c == '(') || (c == '[')) {
                    level++;
                }
                else if ((c == ')') || (c == ']')) {
                    if (level)
                        level--;
                }
                else if (c == ';') {
                    break;
                }
                if (f) {
                    if (c != ' ') {
                        while (i < j)
                            ptr[i++] = ' ';
                        ptr[i++] = c;
                        f = 0;
                    }
                }
                else if (c == ' ') {
                    f = 1;
                }
                else {
                    ptr[i++] = c;
                }
                if (i == 80) {
                    error("Macro argument string too long, max. 80 characters!");
                    return (0);
                }
                j++;
                c = prlnbuf[ip++];
            }
            ptr[i] = '\0';
            ip--;

            /* check if arg is X or Y */
            if (strlen(ptr) && arg) {
                c = tolower(ptr[0]);

                if ((c == 'x') || (c == 'y')) {
                    if ((_stricmp(ptr, "x++") == 0) ||
                        (_stricmp(ptr, "y++") == 0) ||
                        (strlen(ptr) == 1))
                    {
                        arg--;
                        ptr = marg[midx][arg];
    
                        /* check string length */
                        if (strlen(ptr) > 75) {
                            error("Macro argument string too long, max. 80 characters!");
                            return (0);
                        }
    
                        /* attach current arg to the previous one */
                        strcat(ptr, ",");
                        strcat(ptr, marg[midx][arg + 1]);
                        ptr = marg[midx][arg + 1];
                        ptr[0] = '\0';
                    }
                 }
            }
            break;
        }
    }
}

/* install a macro in the hash table */

int
macro_install(void)
{
    unsigned char c;
    int hash = 0;
    int i;

    /* mark the macro name as reserved */
    lablptr->type = MACRO;

    /* check macro name syntax */
    if (strchr(&symbol[1], '.')) {
        error("Invalid macro name!");
        return (0);
    }

    /* calculate symbol hash value */
    for (i = 1; i <= symbol[0]; i++) {
        c = symbol[i];
        hash += c;
        hash  = (hash << 3) + (hash >> 5) + c;
    }
    hash &= 0xFF;

    /* allocate a macro struct */
    mptr = (void *)mem_alloc(sizeof(struct t_macro));
    if (mptr == NULL) {
        error("Out of memory!");
        return (0);
    }

    /* initialize it */
    strcpy(mptr->name, &symbol[1]);
    mptr->line = NULL;
    mptr->next = macro_tbl[hash];
    macro_tbl[hash] = mptr;
    mlptr = NULL;

    /* ok */
    return (1);
}

/* send back the addressing mode of a macro arg */

int
macro_getargtype(char *arg)
{
    struct t_symbol *sym;
    unsigned char c;
    int  i;

    /* skip spaces */
    while (isspace(*arg))
        arg++;

    /* get type */
    switch (toupper(*arg++)) {
    case '\0':
        return (NO_ARG);

    case '"':
        return (ARG_STRING);

    case '#':
        return (ARG_IMM);

    case '[':
        return (ARG_INDIRECT);

    case 'A':
    case 'X':
    case 'Y':
        if (*arg == '\0')
            return (ARG_REG);

    default:
        /* symbol */
        for(i = 0; i < SBOLSZ; i++) {
            c = arg[i];
            if (isdigit(c) && (i == 0))
                break;
            if ((!isalnum(c)) && (c != '_') && (c != '.'))
                break;
        }

        if (i == 0)
            return (ARG_ABS);
        else {
            if (c != '\0')
                return (ARG_ABS);
            else {
                strncpy(&symbol[1], arg, i);
                symbol[0] = i;
                symbol[i+1] = '\0';

                if ((sym = stlook(0)) == NULL)
                    return (ARG_LABEL);
                else {
                    if((sym->type == UNDEF) || (sym->type == IFUNDEF))
                        return (ARG_LABEL);
                    if (sym->bank == RESERVED_BANK)
                        return (ARG_ABS);
                    else
                        return (ARG_LABEL);
                }
            }
        }
    }
}

